﻿using System.Runtime.InteropServices;
using OpenGL.Enums;
using System;

namespace OpenGL.Types
{
    [StructLayout(LayoutKind.Sequential)]
    public struct DisplayList
    {
        private static uint currentlyCreatedList = 0;

        private uint h;

        public void Call()
        {
            GL.CallList(this);
        }

        public void Begin(ListMode listMode)
        {
            if (currentlyCreatedList != 0)
            {
                throw new InvalidOperationException();
            }
            currentlyCreatedList = this.h;

            GL.NewList(this, listMode);
        }
        public void BeginSprite(BeginMode mode)
        {
            GL.Begin(mode);
        }
        public void EndSprite()
        {
            GL.End();
        }
        public void End()
        {
            currentlyCreatedList = 0;

            GL.End();
            GL.EndList();
        }

        public void Color(float r, float g, float b)
        {
            GL.Color(r, g, b);
        }
        public void Color(double r, double g, double b)
        {
            GL.Color(r, g, b);
        }
        public void Color(double r, double g, double b, double alpha)
        {
            GL.Color(r, g, b, alpha);
        }
        public void Color(float r, float g, float b, float alpha)
        {
            GL.Color(r, g, b, alpha);
        }

        public void Vertex(float x, float y, float z)
        {
            GL.Vertex(x, y, z);
        }
        public void Vertex(double x, double y, double z)
        {
            GL.Vertex(x, y, z);
        }
        public void Vertex(float x, float y)
        {
            GL.Vertex(x, y);
        }
        public void Vertex(double x, double y)
        {
            GL.Vertex(x, y);
        }

        private void Normal(float x, float y, float z)
        {
            GL.Normal(x, y, z);
        }
        private void Normal(double x, double y, double z)
        {
            GL.Normal(x, y, z);
        }

        public static DisplayList Generate()
        {
            return GL.GenLists(1);
        }

        public static DisplayList CreateCylinder(float height, float width, int stacks, int slices)
        {
            var result = Generate();
            result.Begin(ListMode.GL_COMPILE);

            result.BeginSprite(BeginMode.GL_TRIANGLE_STRIP);
            float currentHeight = -height / 2, nextHeight = (-height * stacks + 2 * height) / (2 * stacks);
            float currentAngle = 0, nextAngle = (float)(Math.PI * 2 * (slices - 1) / slices);
            for (int i = 0; i < stacks; i++)
            {
                //result.Color(1, 1, 0);
                result.Normal(0, 0, 1);

                result.Vertex(0, currentHeight, width);
                for (int j = 0; j < slices; j++)
                {
                    //result.Color(1f, 0, 0);
                    result.Normal(Math.Sin(currentAngle), 0, Math.Cos(currentAngle));
                    result.Vertex(Math.Sin(currentAngle) * width, nextHeight, Math.Cos(currentAngle) * width);
                    //result.Color(0, 1, 1);
                    result.Normal(0, 0, 1);
                    result.Normal(Math.Sin(nextAngle), 0, Math.Cos(nextAngle));
                    result.Vertex(Math.Sin(nextAngle) * width, currentHeight, Math.Cos(nextAngle) * width);

                    currentAngle = nextAngle;
                    nextAngle = (float)(Math.PI * 2 * (slices - j - 2) / slices);
                }
                //result.Color(1, 0, 1);
                result.Normal(0, 0, 1);
                result.Vertex(0, nextHeight, width);

                currentHeight = nextHeight;
                nextHeight = (-height * stacks + 2 * height * (i + 2)) / (2 * stacks);
            }
            result.EndSprite();

            result.BeginSprite(BeginMode.GL_TRIANGLE_FAN);
            result.Normal(0, -1, 0);
            result.Vertex(0, -height / 2, 0);
            for (int i = 0; i < slices; i++)
            {
                currentAngle = (float)(Math.PI * 2 * (slices - i) / slices);
                result.Normal(0, -1, 0);
                result.Vertex(Math.Sin(currentAngle) * width, -height / 2, Math.Cos(currentAngle) * width);
            }
            result.Normal(0, -1, 0);
            result.Vertex(0, -height / 2, width);
            result.EndSprite();

            result.BeginSprite(BeginMode.GL_TRIANGLE_FAN);
            result.Normal(0, 1, 0);
            result.Vertex(0, height / 2, 0);
            for (int i = 0; i < slices; i++)
            {
                currentAngle = (float)(Math.PI * 2 * i / slices);
                result.Normal(0, 1, 0);
                result.Vertex(Math.Sin(currentAngle) * width, height / 2, Math.Cos(currentAngle) * width);
            }
            result.Normal(0, 1, 0);
            result.Vertex(0, height / 2, width);
            result.EndSprite();

            result.End();
            return result;
        }
        public static DisplayList CreateTorus(float radius1, float radius2, int s1, int s2)
        {
            float[, ,] vertices = new float[s1, s2, 3];
            float a1, a2, r1, r2;
            a1 = 0.0f;
            for (int i = 0; i < s1; i++)
            {
                a1 = (float)(Math.PI * 2.0f * i / s1);

                r1 = (float)(Math.Cos(a1) * radius2 + radius1);
                r2 = radius2 * (float)Math.Sin(a1);

                for (int j = 0; j < s2; j++)
                {
                    a2 = (float)(Math.PI * 2.0f * (j + 1) / s2);

                    vertices[i, j, 0] = r1 * (float)Math.Cos(a2);
                    vertices[i, j, 1] = r1 * (float)Math.Sin(a2);
                    vertices[i, j, 2] = r2;
                }
            }

            var result = Generate();
            result.Begin(ListMode.GL_COMPILE);

            result.BeginSprite(BeginMode.GL_TRIANGLE_STRIP);       

            for (int j = 0; j < s2 - 1; j++)
            {
                result.Vertex(vertices[0, j, 0], vertices[0, j, 1], vertices[0, j, 2]);
                result.Vertex(vertices[0, j + 1, 0], vertices[0, j + 1, 1], vertices[0, j + 1, 2]);
                for (int i = 0; i < s1 - 1; i++)
                {
                    result.Vertex(vertices[i + 1, j, 0], vertices[i + 1, j, 1], vertices[i + 1, j, 2]);
                    result.Vertex(vertices[i + 1, j + 1, 0], vertices[i + 1, j + 1, 1], vertices[i + 1, j + 1, 2]);

                }
                result.Vertex(vertices[0, j, 0], vertices[0, j, 1], vertices[0, j, 2]);
                result.Vertex(vertices[0, j + 1, 0], vertices[0, j + 1, 1], vertices[0, j + 1, 2]);
            }

            result.Vertex(vertices[0, s2 - 1, 0], vertices[0, s2 - 1, 1], vertices[0, s2 - 1, 2]);
            result.Vertex(vertices[0, 0, 0], vertices[0, 0, 1], vertices[0, 0, 2]);
            for (int i = 0; i < s1 - 1; i++)
            {
                result.Vertex(vertices[i + 1, s2 - 1, 0], vertices[i + 1, s2 - 1, 1], vertices[i + 1, s2 - 1, 2]);
                result.Vertex(vertices[i + 1, 0, 0], vertices[i + 1, 0, 1], vertices[i + 1, 0, 2]);

            }
            result.Vertex(vertices[0, s2 - 1, 0], vertices[0, s2 - 1, 1], vertices[0, s2 - 1, 2]);
            result.Vertex(vertices[0, 0, 0], vertices[0, 0, 1], vertices[0, 0, 2]);

            result.EndSprite();

            result.End();
            return result;
        }
    }
}
